/*-------------------------------------------------------------------*/
/*  Include files                                                    */
/*-------------------------------------------------------------------*/
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "InfoNES.h"
#include "InfoNES_System.h"
#include "InfoNES_pAPU.h"

#include "sdl_api.h"
#include "oled.h"

#define USE_EMBED_ROM

/*-------------------------------------------------------------------*/
/*  ROM image file information                                       */
/*-------------------------------------------------------------------*/

char szRomName[ 256 ];
char szSaveName[ 256 ];
int nSRAM_SaveFlag;

// Palette data
WORD NesPalette[ 64 ] =
{
  0x39ce, 0x1071, 0x0015, 0x2013, 0x440e, 0x5402, 0x5000, 0x3c20,
  0x20a0, 0x0100, 0x0140, 0x00e2, 0x0ceb, 0x0000, 0x0000, 0x0000,
  0x5ef7, 0x01dd, 0x10fd, 0x401e, 0x5c17, 0x700b, 0x6ca0, 0x6521,
  0x45c0, 0x0240, 0x02a0, 0x0247, 0x0211, 0x0000, 0x0000, 0x0000,
  0x7fff, 0x1eff, 0x2e5f, 0x223f, 0x79ff, 0x7dd6, 0x7dcc, 0x7e67,
  0x7ae7, 0x4342, 0x2769, 0x2ff3, 0x03bb, 0x0000, 0x0000, 0x0000,
  0x7fff, 0x579f, 0x635f, 0x6b3f, 0x7f1f, 0x7f1b, 0x7ef6, 0x7f75,
  0x7f94, 0x73f4, 0x57d7, 0x5bf9, 0x4ffe, 0x0000, 0x0000, 0x0000
};

int InfoNES_Menu()
{
  return 0;
}

void *InfoNES_MemorySet( void *dest, int c, int count )
{
	memset( dest, c, count);  
	return dest;
}

void InfoNES_MessageBox( char *pszMsg, ... )
{

}

#ifndef USE_EMBED_ROM

int InfoNES_ReadRom( const char *pszFileName )
{
/*
 *  Read ROM image file
 *
 *  Parameters
 *    const char *pszFileName          (Read)
 *
 *  Return values
 *     0 : Normally
 *    -1 : Error
 */

  FILE *fp;

  /* Open ROM file */
  fp = fopen( pszFileName, "rb" );
  if ( fp == NULL )
    return -1;

  /* Read ROM Header */
  fread( &NesHeader, sizeof NesHeader, 1, fp );
  if ( memcmp( NesHeader.byID, "NES\x1a", 4 ) != 0 )
  {
    /* not .nes file */
    fclose( fp );
    return -1;
  }

  /* Clear SRAM */
  BYTE_ARR_memset( SRAM_, 0, SRAM_SIZE );

  /* If trainer presents Read Triner at 0x7000-0x71ff */
  if ( NesHeader.byInfo1 & 4 )
  {
    fread( &SRAM[ 0x1000 ], 512, 1, fp );
  }

  /* Allocate Memory for ROM Image */
  ROM = (BYTE *)malloc( NesHeader.byRomSize * 0x4000 );

  /* Read ROM Image */
  fread( ROM, 0x4000, NesHeader.byRomSize, fp );

  if ( NesHeader.byVRomSize > 0 )
  {
    /* Allocate Memory for VROM Image */
    VROM = (BYTE *)malloc( NesHeader.byVRomSize * 0x2000 );

    /* Read VROM Image */
    fread( VROM, 0x2000, NesHeader.byVRomSize, fp );
  }

  /* File close */
  fclose( fp );

  /* Successful */
  return 0;
}








/*===================================================================*/
/*                                                                   */
/*           InfoNES_ReleaseRom() : Release a memory for ROM         */
/*                                                                   */
/*===================================================================*/
void InfoNES_ReleaseRom()
{
/*
 *  Release a memory for ROM
 *
 */

  if ( ROM )
  {
    free( ROM );
    ROM = NULL;
  }

  if ( VROM )
  {
    free( VROM );
    VROM = NULL;
  }
}

#else
/* USE_EMBED_ROM*/


int InfoNES_ReadRom( const char *pszFileName )
{
/*
 *  Read ROM image file
 *
 *  Parameters
 *    const char *pszFileName          (Read)
 *
 *  Return values
 *     0 : Normally
 *    -1 : Error
 */

  extern const BYTE nes_rom[];
  /* Read ROM Header */
  BYTE * rom = (BYTE*)nes_rom;
  memcpy( &NesHeader, rom, sizeof(NesHeader));
  if ( memcmp( NesHeader.byID, "NES\x1a", 4 ) != 0 )
  {
    /* not .nes file */
    return -1;
  }
  rom += sizeof(NesHeader);

  /* Clear SRAM */
  BYTE_ARR_memset( SRAM_, 0, 0, SRAM_SIZE );

  /* If trainer presents Read Triner at 0x7000-0x71ff */
  if ( NesHeader.byInfo1 & 4 )
  {
    //memcpy( &SRAM[ 0x1000 ], rom, 512);
	rom += 512;
  }

  /* Allocate Memory for ROM Image */
  ROM = rom;
  rom += NesHeader.byRomSize * 0x4000;

  if ( NesHeader.byVRomSize > 0 )
  {
    /* Allocate Memory for VROM Image */
	VROM = (BYTE*)rom;
	rom += NesHeader.byVRomSize * 0x2000;
  }

  /* Successful */
  return 0;
}

void InfoNES_ReleaseRom()
{
}



#endif



/*===================================================================*/
/*                                                                   */
/*            InfoNES_Wait() : Wait Emulation if required            */
/*                                                                   */
/*===================================================================*/
void InfoNES_Wait()
{
}

void InfoNES_PadState( DWORD *pdwPad1, DWORD *pdwPad2, DWORD *pdwSystem )
{

}

#ifdef ARDUINO
#include <Arduino.h>
#endif

int istest = 0;

#define PACK_LEN 1   //smaller than 240, 240 % PACK_LEN == 0 
uint8_t images[240 * 2 * PACK_LEN];
static long lasttime = 0;
//hard spi, not batch: 893ms
//hard spi, batch: 123ms (PACK_LEN == 2)
//spiClk:(8MHz)
//draw delta == 121 
//frame delta == 213
//spiClk:(16MHz)
//draw delta == 64 
//frame delta == 156 
//spiClk:(32MHz)
//draw delta == 41 
//frame delta == 133  
//spiClk:(64MHz)
//draw delta == 30 
//frame delta == 101 
void InfoNES_LoadFrame()
{
  //static int led = LOW;
  //led = !led;
  //digitalWrite(LED_BUILTIN, led);
  int t = millis();
  if (lasttime != 0) {
    extern void test2(long t);
    test3(lasttime);
  }
  lasttime = millis();
  
#if !NO_WORKFRAME

  //memcpy( pScreenMem, WorkFrame, NES_DISP_WIDTH * NES_DISP_HEIGHT * 2 );
	/*
  int i, j;
  for (j = 0; j < NES_DISP_HEIGHT; ++j)
  {
	  for (i = 0; i < NES_DISP_WIDTH; ++i)
	  {
			WORD word = WorkFrame[i];
			if (word != 0)
			{
				printf("%ld\n", word);
			}
	  }
  }*/

//https://blog.csdn.net/iteye_4515/article/details/81800882
#define RGB565toRGB(rgb565)    ((DWORD)(((BYTE)((((rgb565)&0xF800)>>11)<<3)|((WORD)((BYTE)((((rgb565)&0x07E0)>>5)<<2))<<8))|(((DWORD)(BYTE)(((rgb565)&0x001F)<<3))<<16)))
#define RGB555toRGB(rgb555)    ((DWORD)(((BYTE)(((rgb555)>>7)&0xF8)|((WORD)((BYTE)(((rgb555)>>2)&0xF8))<<8))|(((DWORD)(BYTE)(((rgb555)<<3)&0xF8))<<16)))
#define RGBtoRGB565(rgb) ((WORD)(((((WORD)((rgb)>>3))&(0x1F))<<11)|((((WORD)((rgb)>>10))&(0x3F))<<5)|(((WORD)((rgb)>>19))&(0x1F))))

const int __Gnbmp_height = 240; //tft lcd height
const int __Gnbmp_width  = 240; //tft lcd width

#ifndef _MSC_VER
#define USE_565
#endif

  int x, y;	
	CanvasAddressSet(0, 0, __Gnbmp_width-1,__Gnbmp_height-1);
  for(y=0; y<NES_DISP_HEIGHT && y<__Gnbmp_height; y++){
    for(x=0; x<NES_DISP_WIDTH && x<__Gnbmp_width; x++){  
#ifdef USE_565
	  DWORD rgb;
#endif
      WORD word = BYTE_ARR_GetWord_work(WorkFrame_, y * NES_DISP_WIDTH + x);
	  BYTE byte = (BYTE)((word & 0xFF00) >> 8);
	  BYTE byte2 = (BYTE)((word & 0xFF) >> 0);
	  //word = byte2 | byte2 << 8;
	  if (byte != 0 && !istest)
	  {
		  DWORD rgb = RGB555toRGB(word);
		  BYTE r = (BYTE)((rgb >>  0) & 0xff);
		  BYTE g = (BYTE)((rgb >>  8) & 0xff);
		  BYTE b = (BYTE)((rgb >> 16) & 0xff);
		  WORD rgb565 = RGBtoRGB565(rgb);
	  	  printf("hello %d\n", word);
		  istest = 1;
	  }
#ifndef USE_565
		CanvasDrawPixel(x, y, RGB555toRGB(word), 0xff);
#else
		rgb = RGB555toRGB(word);
		word = RGBtoRGB565(rgb);
	  CanvasDrawPixel(x, y, word, 0xff);
#endif
    }
  }


#else /*===================NO_WORKFRAME===================*/


#if PAL_DRAW
extern unsigned char *WorkFrame_p; //NES_DISP_WIDTH * NES_DISP_HEIGHT
extern unsigned short WorkFrame_pal[256];
const int __Gnbmp_height = 240; //tft lcd height
const int __Gnbmp_width  = 240; //tft lcd width
int x, y, kk;

  Address_set(0, 0, __Gnbmp_width-1,__Gnbmp_height-1);
#if !USE_ESP32_HSPI   
  for(y=0; y<NES_DISP_HEIGHT && y<__Gnbmp_height; y++){
    for(x=0; x<NES_DISP_WIDTH && x<__Gnbmp_width; x++){  
      BYTE byte = WorkFrame_p[y * NES_DISP_WIDTH + x];
      WORD color = WorkFrame_pal[byte];
      OLED_WR_DATA8((char)((color >> 8) & 0xff));
      OLED_WR_DATA8((char)(color & 0xff));
    }
  }
#else
     OLED_BEGIN();
     OLED_DC_Set();
     for(kk=0; kk<NES_DISP_HEIGHT/PACK_LEN; ++kk) { 
     //uint8_t images[240 * 2 * PACK_LEN];
     for(y=0; y<PACK_LEN; y++){
      for(x=0; x<NES_DISP_WIDTH && x<__Gnbmp_width; x++){  
        BYTE byte = WorkFrame_p[(y + kk * PACK_LEN) * NES_DISP_WIDTH + x]; //NES_DISP_WIDTH*NES_DISP_HEIGHT == 256*240
        WORD color = WorkFrame_pal[byte];
        images[y * __Gnbmp_width * 2 + x * 2] = (char)((color >> 8) & 0xff); //__Gnbmp_width*__Gnbmp_height == 240*240
        images[y * __Gnbmp_width * 2 + x * 2 + 1] = (char)(color & 0xff);
      }
    }
    OLED_transfer(images, sizeof(images));
     }
     OLED_END(); 
#endif

#endif


#endif
  extern void test2(long t);
  test2(t);
}

void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 ) 
{

}

int InfoNES_SoundOpen( int samples_per_sync, int sample_rate ) 
{
	return 1;
}

void InfoNES_SoundInit( void ) {}

void InfoNES_SoundClose( void ) {}

void *InfoNES_MemoryCopy( void *dest, const void *src, int count )
{
	memcpy( dest, src, count );
	return dest;
}

//unsigned char BUFFER_RAM_[RAM_SIZE];
//unsigned char BUFFER_SRAM_[SRAM_SIZE];
//unsigned char BUFFER_PPURAM_[PPURAM_SIZE];
//unsigned char BUFFER_ChrBuf_[256 * 2 * 8 * 8];
//unsigned char BUFFER_WorkFrame_[NES_DISP_WIDTH * NES_DISP_HEIGHT * 2];

#if PAL_DRAW
unsigned char *WorkFrame_p = NULL;
unsigned short WorkFrame_pal[256];
#endif

//https://docs.espressif.com/projects/esp-idf/zh_CN/v3.1.1/api-reference/system/mem_alloc.html
//https://another.maple4ever.net/archives/category/arduino/
#ifdef ARDUINO
#include <arduino.h>
#endif

int InfoNES_main_entry(int argc, const char **argv)
{
	const char *szFileName = "MARIO.NES";

#ifdef ARDUINO
  digitalWrite(LED_BUILTIN, HIGH);  
#endif
  Mem_Init();
	//???DRAM
	
	//RAM_ = BYTE_ARR_calloc(RAM_SIZE, 1); 
	RAM_ = BYTE_ARR_calloc_from(malloc(RAM_SIZE));
  BYTE_ARR_memset(RAM_, 0, 0, RAM_SIZE);
	
	//SRAM_ = BYTE_ARR_calloc(SRAM_SIZE, 1); 
	SRAM_ = BYTE_ARR_calloc_from(malloc(SRAM_SIZE)); 
  BYTE_ARR_memset(SRAM_, 0, 0, SRAM_SIZE);
  
	//PPURAM_ = BYTE_ARR_calloc_from(BUFFER_PPURAM_);//PPURAM_SIZE, 1); 
	PPURAM_ = BYTE_ARR_calloc_from(malloc(PPURAM_SIZE)); 
	BYTE_ARR_memset(PPURAM_, 0, 0, PPURAM_SIZE);
  
	//ChrBuf_ = BYTE_ARR_calloc(256 * 2 * 8 * 8, 1);
	ChrBuf_ = BYTE_ARR_calloc_from(malloc(256 * 2 * 8 * 8));
  BYTE_ARR_memset(ChrBuf_, 0, 0, 256 * 2 * 8 * 8);

#if !NO_WORKFRAME
#ifdef ARDUINO
  //extern void test();
  //test();
  WorkFrame_p = ps_malloc(240*256*2);//heap_caps_malloc(240*256*2, MALLOC_CAP_INTERNAL);
#else
  WorkFrame_p = malloc(240*256*2);
#endif
  if (WorkFrame_p == NULL) {
#ifdef ARDUINO
    while (1)
    {
      digitalWrite(LED_BUILTIN, HIGH);  
      delay(50);
      digitalWrite(LED_BUILTIN, LOW);  
      delay(50);
    }
#endif
    return 0;
  }
	WorkFrame_ = BYTE_ARR_calloc_from(WorkFrame_p);
  BYTE_ARR_memset(WorkFrame_, 0, 0, 240*256*2);
#else  /*===============NO_WORKFRAME=====================*/

#if PAL_DRAW
  WorkFrame_p = malloc(240*256);
  memset(WorkFrame_pal, 0, sizeof(WorkFrame_pal));
  if (WorkFrame_p == NULL) {
#ifdef ARDUINO
    while (1)
    {
      digitalWrite(LED_BUILTIN, HIGH);  
      delay(50);
      digitalWrite(LED_BUILTIN, LOW);  
      delay(50);
    }
#endif
    return 0;
  }
#endif
  
	WorkFrame_ = BYTE_ARR_calloc_work(NES_DISP_WIDTH * NES_DISP_HEIGHT * 2, 1);//(240*256*2);
#endif

#ifdef ARDUINO
  digitalWrite(LED_BUILTIN, LOW);  
#endif  

	if ( InfoNES_Load( "MARIO.NES" ) == 0 )
    {
        // Set a ROM image name
        strcpy( szRomName, szFileName );
		InfoNES_Main();
		InfoNES_Fin();
	}
	return 0;
}
